(function ( exports ) {

    //region Xrm2011MetadataQueryValue

    function Xrm2011MetadataQueryValue () {
        COSA.XrmValueBase.call(this);
    }

    COSA.Util.deriveOverride(COSA.XrmValueBase, Xrm2011MetadataQueryValue, {
        namespace:'http://schemas.microsoft.com/xrm/2011/Metadata/Query'
    });

    //endregion Xrm2011MetadataQueryValue

    //region LabelQuery

    function LabelQueryExpression ( languages, missingLabelBehavior ) {
        Xrm2011MetadataQueryValue.call(this, 'LabelQuery', false);

        this.languages = languages;
        this.missingLabelBehavior = missingLabelBehavior;
    }

    COSA.Util.deriveOverride(Xrm2011MetadataQueryValue, LabelQueryExpression, {
        type:'LabelQuery',
        getTemplate: function ( sc, ltr ) {
            var languagesNode = { tag: ltr.tag('FilterLanguages') },
                missingLabelBehaviorNode = { tag: ltr.tag('MissingLabelBehavior') };


            sc.snapshot();

            languagesNode.node = (new COSA.IntCoolection(this.languages)).serialize(sc);
            sc.appendToParent(languagesNode);

            sc.restore();

            if ( typeof this.missingLabelBehavior || this.missingLabelBehavior === null ) {
                missingLabelBehaviorNode.attr = { 'i:nil':'true' };
            }
            else {
                missingLabelBehaviorNode.val = this.missingLabelBehavior;
            }

            return [ languagesNode, missingLabelBehaviorNode]
        }
    });

    //endregion LabelQuery

    //region RelationshipQuery

    function RelationshipQueryExpression ( criteria, properties ) {
        Xrm2011MetadataQueryValue.call(this, 'RelationshipQuery');

        this.criteria = criteria;
        this.properties = properties;
    }

    COSA.Util.deriveOverride(Xrm2011MetadataQueryValue, RelationshipQueryExpression, {
        type:'RelationshipQuery',
        getTemplate: function ( sc, ltr ) {
            var propertiesNode = { tag: ltr.tag('Properties') };
            if ( typeof this.properties == 'undefined' || this.properties === null || this.properties.length === 0 ) {
                propertiesNode.attr = { 'i:nil':'true' };
            }
            else {
                propertiesNode.nodes = this.properties.serialize(sc);
            }
            return [{
                tag: ltr.tag('Criteria'),
                nodes: this.criteria.serialize(sc)
            }, propertiesNode]
        }
    });

    //endregion RelationshipQuery

    //region AttributeQueryExpression

    function AttributeQueryExpression ( criteria, properties ) {
        Xrm2011MetadataQueryValue.call(this);

        this.criteria = criteria;
        this.properties = properties;
    }

    COSA.Util.deriveOverride(Xrm2011MetadataQueryValue, AttributeQueryExpression, {
        type:'AttributeQuery',
        getTemplate: function ( sc, ltr ) {
            return [{
                tag: ltr.tag('Criteria'),
                nodes: this.criteria.serialize(sc)
            }, {
                tag: ltr.tag('Properties'),
                nodes: (new MetadataPropertiesExpression(this.properties)).serialize(sc)
            }];
        }
    });


    //endregion AttributeQueryExpression

    //region MetadataPropertiesExpression

    function MetadataPropertiesExpression ( items ) {
        Xrm2011MetadataQueryValue.call(this);
        this.items = items;
    }

    COSA.Util.deriveOverride(Xrm2011MetadataQueryValue, MetadataPropertiesExpression, {
        type:'Properties',
        getTemplate:function ( sc, ltr ) {
            var propertyNamesNode = { tag: ltr.tag('PropertyNames')},
                allPropertiesNode = {
                    tag: ltr.tag('AllProperties'),
                    val: (this.items.length === 0).toString()
                };
            sc.snapshot();

            propertyNamesNode.nodes = (new COSA.StringCollection(this.items)).serialize(sc);
            sc.appendToParent(propertyNamesNode);

            sc.restore();

            return [ allPropertiesNode, propertyNamesNode ];
        }
    });

    //endregion MetadataPropertiesExpression

    //region MetadataFilterExpression

    function MetadataFilterExpression ( filterOperator, conditions, filters  ) {
        Xrm2011MetadataQueryValue.call(this, 'MetadataFilterExpression', false);

        this.filterOperator = filterOperator;
        this.conditions = conditions;
        this.filters = filters;
    }

    COSA.Util.deriveOverride(Xrm2011MetadataQueryValue, MetadataFilterExpression, {
        type:'MetadataFilterExpression',
        getTemplate:function ( sc, ltr ) {
            return [{
                tag: ltr.tag('Conditions'),
                nodes: (new COSA.MetadataConditionCollection(this.conditions)).serialize(sc)
            }, {
                tag: ltr.tag('FilterOperator'),
                val: this.filterOperator
            }, {
                tag: ltr.tag('Filters'),
                nodes: (new COSA.FiltersCollection(this.filters)).serialize(sc)
            }];
        }
    });

    //endregion MetadataFilterExpression

    //region MetadataConditionExpression

    function MetadataConditionExpression ( conditionOperator, propertyName, value ) {
        Xrm2011MetadataQueryValue.call(this);

        this.conditionOperator = conditionOperator;
        this.propertyName = propertyName;
        this.value = value;
    }

    COSA.Util.deriveOverride(Xrm2011MetadataQueryValue, MetadataConditionExpression, {
        type:'MetadataConditionExpression',
        getTemplate:function ( sc, ltr ) {
            var valueNode = { tag: ltr.tag('Value')},
                valueNodes;

            if ( this.value === null || typeof this.value == 'undefined' ) {
                valueNode.attr = {'i:nil':'true'};
            }
            else {
                sc.snapshot();

                valueNodes = this.value.serialize(sc);
                sc.appendToParent(valueNode);

                if ( Array.isArray(valueNodes) ) {
                    valueNode.nodes = valueNodes;
                }
                else {
                    valueNode.val = valueNodes;
                }

                sc.restore();
            }

            return [{
                tag: ltr.tag('ConditionOperator'),
                val: this.conditionOperator
            }, {
                tag: ltr.tag('PropertyName'),
                val: this.propertyName
            }, valueNode];
        }
    });

    //endregion MetadataConditionExpression

    //region DeletedMetadataFilters

    /**
     * DeleteMetadataFilters
     * @param {string} value Value
     * @prop {string} value Value
     * @memberOf COSA
     * @constructor DeletedMetadataFilters
     */
    function DeletedMetadataFilters ( value ) {
        Xrm2011MetadataQueryValue.call(this);
        this.value = value;
    }

    COSA.Util.deriveOverride(Xrm2011MetadataQueryValue, DeletedMetadataFilters, {
        getTemplate:function () {
            return this.value;
        }
    });

    //endregion DeletedMetadataFilters

    //region EntityQueryExpression

    function EntityQueryExpression ( criteria, properties, attributeQuery, labelQuery, relationshipQuery ) {
        Xrm2011MetadataQueryValue.call(this, 'EntityQueryExpression', true);

        this.criteria = criteria;
        this.properties = properties;
        this.attributeQuery = attributeQuery;
        this.labelQuery = labelQuery;
        this.relationshipQuery = relationshipQuery;
    }

    COSA.Util.deriveOverride(Xrm2011MetadataQueryValue, EntityQueryExpression, {
        type:'EntityQueryExpression',
        requireTypeDef: true,
        getTemplate:function ( sc, ltr ) {
            var attributeQueryNode = { tag: ltr.tag('AttributeQuery') },
                labelQueryNode = { tag: ltr.tag('LabelQuery')},
                relationshipQueryNode = { tag: ltr.tag('RelationshipQuery')};

            if ( typeof this.attributeQuery != 'undefined' && this.attributeQuery != null ) {
                attributeQueryNode.nodes = this.attributeQuery.serialize(sc);
            }
            else {
                attributeQueryNode.attr = { 'i:nil':true.toString() };
            }

            if ( typeof this.labelQuery != 'undefined' && this.labelQuery != null ) {
                labelQueryNode.nodes = this.labelQuery.serialize(sc);
            }
            else {
                labelQueryNode.attr = { 'i:nil':true.toString() };
            }

            if ( typeof this.relationshipQuery != 'undefined' && this.relationshipQuery != null ) {
                relationshipQueryNode.nodes = this.relationshipQuery.serialize(sc);
            }
            else {
                relationshipQueryNode.attr = { 'i:nil':true.toString() };
            }

            return [{
                tag: ltr.tag('Criteria'),
                nodes: this.criteria.serialize(sc)
            }, {
                tag: ltr.tag('Properties'),
                nodes: (new MetadataPropertiesExpression(this.properties)).serialize(sc)
            }, attributeQueryNode, labelQueryNode, relationshipQueryNode];
        }
    });

    //endregion EntityQueryExpression

    exports.LabelQueryExpression = LabelQueryExpression;
    exports.RelationshipQueryExpression = RelationshipQueryExpression;
    exports.AttributeQueryExpression = AttributeQueryExpression;
    exports.MetadataPropertiesExpression = MetadataPropertiesExpression;
    exports.MetadataFilterExpression = MetadataFilterExpression;
    exports.MetadataConditionExpression = MetadataConditionExpression;
    exports.DeletedMetadataFilters = DeletedMetadataFilters;
    exports.EntityQueryExpression = EntityQueryExpression;

})(COSA);
